#include <os/list.h>
#include <os/lock.h>
#include <os/sched.h>
#include <os/time.h>
#include <os/mm.h>
#include <screen.h>
#include <printk.h>
#include <assert.h>

pcb_t pcb[NUM_MAX_TASK];

//task5
tcb_t tcb[2];

const ptr_t pid0_stack = INIT_KERNEL_STACK + PAGE_SIZE;
pcb_t pid0_pcb = {
    .pid = 0,
    .kernel_sp = (ptr_t)pid0_stack,
    .user_sp = (ptr_t)pid0_stack
};

LIST_HEAD(ready_queue);
LIST_HEAD(sleep_queue);

/* current running task PCB */
pcb_t * volatile current_running;

/* global process id */
pid_t process_id = 1;

void do_scheduler(void)
{
    // TODO: [p2-task3] Check sleep queue to wake up PCBs
    check_sleeping();

    // TODO: [p2-task1] Modify the current_running pointer.
    list_node_t* node_ptr = ready_queue.next;
    DE_LIST(ready_queue);
    pcb_t* next_pcb     = (pcb_t*)((ptr_t)node_ptr - 2*sizeof(reg_t));
    pcb_t* previous_pcb = current_running;
    next_pcb->status    = TASK_RUNNING;
    
    if(previous_pcb != &pid0_pcb && previous_pcb->status == TASK_RUNNING){
        previous_pcb->status= TASK_READY;
        EN_LIST(ready_queue, previous_pcb->list);
    }

    current_running    = next_pcb;

    // TODO: [p2-task1] switch_to current_running
    switch_to(previous_pcb, next_pcb);

}

void do_sleep(uint32_t sleep_time)
{
    // TODO: [p2-task3] sleep(seconds)
    // NOTE: you can assume: 1 second = 1 `timebase` ticks
    // 1. block the current_running
    // 2. set the wake up time for the blocked task
    // 3. reschedule because the current_running is blocked.
    
    // set the status to BLOCKED
    current_running->status = TASK_BLOCKED;
    // add the list to sleep_queue
    EN_LIST(sleep_queue, current_running->list);
    // set the wakeup time
    current_running->wakeup_time = get_timer() + sleep_time;
    
    do_scheduler();
    
    
}

void do_block(list_node_t *pcb_node, list_head *queue)
{
    // TODO: [p2-task2] block the pcb task into the block queue
    pcb_t* pcb_ptr  = (pcb_t*)((ptr_t)pcb_node - 2*sizeof(reg_t));
    pcb_ptr->status = TASK_BLOCKED;

    EN_LIST((*queue),(*pcb_node));
 
}

void do_unblock(list_node_t *pcb_node)
{
    // TODO: [p2-task2] unblock the `pcb` from the block queue
    pcb_node->prev->next = pcb_node->next;
    pcb_node->next->prev = pcb_node->prev;

    pcb_t* pcb_ptr  = (pcb_t*)((ptr_t)pcb_node - 2*sizeof(reg_t));
    pcb_ptr->status = TASK_READY;
    EN_LIST(ready_queue,(*pcb_node));

}

extern void ret_from_exception();

void thread_create(ptr_t function, int pid){
    //initialize a tcb_t of the new thread
    //alloc the space for user_stack and kernel_stack
    ptr_t ret1, ret2;
    ret1 = allocKernelPage(1);
    ret2 = allocUserPage(1);
   
   //initialize the user_stack and kernel_stack
    regs_context_t *pt_regs =
        (regs_context_t *)((ptr_t)(ret1 + PAGE_SIZE) - sizeof(regs_context_t));
    
    *(reg_t*)((ptr_t)pt_regs + 8)   = (reg_t) function; //set the value of ra
    *(reg_t*)((ptr_t)pt_regs + 16)  = (reg_t) (ret2 + PAGE_SIZE);  //set the value of sp
    *(reg_t*)((ptr_t)pt_regs + 256) = (reg_t) 0x2; //set the sstatus's SPIE 1, and set the sstatus's SPP 0;
    *(reg_t*)((ptr_t)pt_regs + 264) = (reg_t) function; //set the value of sepc
    
    
    switchto_context_t *pt_switchto =
        (switchto_context_t *)((ptr_t)pt_regs - sizeof(switchto_context_t));
    
    //initialize the pcb->kernel_sp and pcb->user_sp
    tcb[pid-1].kernel_sp = (reg_t)pt_switchto;
    tcb[pid-1].user_sp   = (reg_t)(ret2 + PAGE_SIZE);
    
    //initialize the value of reg ra and reg sp
    *(reg_t*)((ptr_t)pt_switchto)     = (reg_t)ret_from_exception;
    *(reg_t*)((ptr_t)pt_switchto + 8) = (reg_t)pt_switchto;
    
    EN_LIST(ready_queue, tcb[pid-1].list);


    

}
